Passed
Push — feature/settings-2fa ( 31e075...1e717f )
by Grant
19:37 queued 06:19
created

ReviewApplicationsRoot   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 172
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 136
dl 0
loc 172
rs 10
c 0
b 0
f 0
wmc 15

7 Functions

Rating   Name   Duplication   Size   Complexity  
A render 0 22 1
A handleBulkStatusChange 0 42 3
A submitReview 0 21 1
A handleNotesChange 0 17 3
A handleStatusChange 0 17 3
A handleSavingStatusChange 0 12 2
A updateReviewState 0 13 2
1
/* eslint camelcase: "off", @typescript-eslint/camelcase: "off" */
2
import React from "react";
3
import ReactDOM from "react-dom";
4
5
// Internationalizations
6
import { injectIntl, defineMessages, WrappedComponentProps } from "react-intl";
7
8
import camelCase from "lodash/camelCase";
9
import Swal from "sweetalert2";
10
import {
11
  Job,
12
  Application,
13
  ReviewStatus,
14
  ApplicationReview,
15
} from "../../models/types";
16
import ReviewApplications from "./ReviewApplications";
17
import { find } from "../../helpers/queries";
18
import * as routes from "../../helpers/routes";
19
import { classificationString } from "../../models/jobUtil";
20
import { axios } from "../../api/base";
21
import IntlContainer from "../../IntlContainer";
22
23
interface ReviewApplicationsProps {
24
  job: Job;
25
  initApplications: Application[];
26
  reviewStatuses: ReviewStatus[];
27
}
28
29
interface ReviewApplicationsState {
30
  applications: Application[];
31
  savingStatuses: { applicationId: number; isSaving: boolean }[];
32
}
33
34
interface ReviewSubmitForm {
35
  review_status_id?: number | null;
36
  notes?: string | null;
37
}
38
39
const localizations = defineMessages({
40
  oops: {
41
    id: "alert.oops",
42
    defaultMessage: "Save",
43
    description: "Dynamic Save button label",
44
  },
45
  somethingWrong: {
46
    id: "apl.reviewSaveFailed",
47
    defaultMessage:
48
      "Something went wrong while saving a review. Try again later.",
49
    description: "Dynamic Save button label",
50
  },
51
});
52
53
class ReviewApplicationsRoot extends React.Component<
54
  ReviewApplicationsProps & WrappedComponentProps,
55
  ReviewApplicationsState
56
> {
57
  public constructor(props: ReviewApplicationsProps & WrappedComponentProps) {
58
    super(props);
59
    this.state = {
60
      applications: props.initApplications,
61
      savingStatuses: props.initApplications.map(application => ({
62
        applicationId: application.id,
63
        isSaving: false,
64
      })),
65
    };
66
    this.handleStatusChange = this.handleStatusChange.bind(this);
67
    this.handleBulkStatusChange = this.handleBulkStatusChange.bind(this);
68
    this.handleNotesChange = this.handleNotesChange.bind(this);
69
    this.updateReviewState = this.updateReviewState.bind(this);
70
    this.handleSavingStatusChange = this.handleSavingStatusChange.bind(this);
71
  }
72
73
  protected updateReviewState(
74
    applicationId: number,
75
    review: ApplicationReview,
76
  ): void {
77
    const { applications } = this.state;
78
    const updatedApplications = applications.map(application => {
79
      if (application.id === applicationId) {
80
        return Object.assign(application, { application_review: review });
81
      }
82
      return { ...application };
83
    });
84
    this.setState({ applications: updatedApplications });
85
  }
86
87
  protected handleSavingStatusChange(
88
    applicationId: number,
89
    isSaving: boolean,
90
  ): void {
91
    const { savingStatuses } = this.state;
92
    const statuses = savingStatuses.map(item =>
93
      item.applicationId === applicationId
94
        ? { applicationId, isSaving }
95
        : { ...item },
96
    );
97
    this.setState({ savingStatuses: statuses });
98
  }
99
100
  protected submitReview(
101
    applicationId: number,
102
    review: ReviewSubmitForm,
103
  ): void {
104
    const { intl } = this.props;
105
    this.handleSavingStatusChange(applicationId, true);
106
    axios
107
      .put(routes.applicationReviewUpdate(intl.locale, applicationId), review)
108
      .then(response => {
109
        const newReview = response.data as ApplicationReview;
110
        this.updateReviewState(applicationId, newReview);
111
        this.handleSavingStatusChange(applicationId, false);
112
      })
113
      .catch(() => {
114
        Swal.fire({
115
          icon: "error",
116
          title: intl.formatMessage(localizations.oops),
117
          text: intl.formatMessage(localizations.somethingWrong),
118
        });
119
        this.handleSavingStatusChange(applicationId, false);
120
      });
121
  }
122
123
  protected handleStatusChange(
124
    applicationId: number,
125
    statusId: number | null,
126
  ): void {
127
    const { applications } = this.state;
128
    const application = find(applications, applicationId);
129
    if (application === null) {
130
      return;
131
    }
132
    const oldReview = application.application_review
133
      ? application.application_review
134
      : {};
135
    const submitReview = Object.assign(oldReview, {
136
      review_status_id: statusId,
137
    });
138
    this.submitReview(applicationId, submitReview);
139
  }
140
141
  protected handleBulkStatusChange(
142
    applicationIds: number[],
143
    statusId: number | null,
144
  ): void {
145
    const { applications } = this.state;
146
    const { intl } = this.props;
147
    const changedApplications = applications.filter(application =>
148
      applicationIds.includes(application.id),
149
    );
150
    let errorThrown = false;
151
    changedApplications.map(application => {
152
      const oldReview = application.application_review
153
        ? application.application_review
154
        : {};
155
      const submitReview = Object.assign(oldReview, {
156
        review_status_id: statusId,
157
      });
158
      this.handleSavingStatusChange(application.id, true);
159
      const request = axios
160
        .put(
161
          routes.applicationReviewUpdate(intl.locale, application.id),
162
          submitReview,
163
        )
164
        .then(response => {
165
          const newReview = response.data as ApplicationReview;
166
          this.updateReviewState(application.id, newReview);
167
          this.handleSavingStatusChange(application.id, false);
168
        })
169
        .catch(() => {
170
          this.handleSavingStatusChange(application.id, false);
171
          // Only show error modal first time a request fails
172
          if (!errorThrown) {
173
            errorThrown = true;
174
            Swal.fire({
175
              icon: "error",
176
              title: intl.formatMessage(localizations.oops),
177
              text: intl.formatMessage(localizations.somethingWrong),
178
            });
179
          }
180
        });
181
      return request;
182
    });
183
  }
184
185
  protected handleNotesChange(
186
    applicationId: number,
187
    notes: string | null,
188
  ): void {
189
    const { applications } = this.state;
190
    const application = find(applications, applicationId);
191
    if (application === null) {
192
      return;
193
    }
194
    const oldReview = application.application_review
195
      ? application.application_review
196
      : {};
197
    const submitReview = Object.assign(oldReview, {
198
      notes,
199
    });
200
    this.submitReview(applicationId, submitReview);
201
  }
202
203
  public render(): React.ReactElement {
204
    const { applications, savingStatuses } = this.state;
205
    const { reviewStatuses, job } = this.props;
206
    const { intl } = this.props;
207
208
    const reviewStatusOptions = reviewStatuses.map(status => ({
209
      value: status.id,
210
      label: camelCase(status.name),
211
    }));
212
213
    return (
214
      <ReviewApplications
215
        title={job[intl.locale].title}
216
        classification={classificationString(job)}
217
        closeDateTime={job.close_date_time}
218
        applications={applications}
219
        reviewStatusOptions={reviewStatusOptions}
220
        onStatusChange={this.handleStatusChange}
221
        onBulkStatusChange={this.handleBulkStatusChange}
222
        onNotesChange={this.handleNotesChange}
223
        savingStatuses={savingStatuses}
224
      />
225
    );
226
  }
227
}
228
229
if (document.getElementById("review-applications-container")) {
230
  const container = document.getElementById(
231
    "review-applications-container",
232
  ) as HTMLElement;
233
  if (
234
    container.hasAttribute("data-job") &&
235
    container.hasAttribute("data-applications") &&
236
    container.hasAttribute("data-review-statuses") &&
237
    container.hasAttribute("data-locale")
238
  ) {
239
    const job = JSON.parse(container.getAttribute("data-job") as string);
240
    const applications = JSON.parse(container.getAttribute(
241
      "data-applications",
242
    ) as string);
243
    const reviewStatuses = JSON.parse(container.getAttribute(
244
      "data-review-statuses",
245
    ) as string);
246
    const language = container.getAttribute("data-locale") as string;
247
    const IntlReviewApplicationsRoot = injectIntl(ReviewApplicationsRoot);
248
    ReactDOM.render(
249
      <IntlContainer locale={language}>
250
        <IntlReviewApplicationsRoot
251
          job={job}
252
          initApplications={applications}
253
          reviewStatuses={reviewStatuses}
254
        />
255
      </IntlContainer>,
256
      container,
257
    );
258
  }
259
}
260
261
export default injectIntl(ReviewApplicationsRoot);
262